use crate::{
    errors::cons_error,
    mdb::{MClient, ModelEx, MDB},
    middlewares::session::CacheData,
    models::{
        account::AccountModel,
        cache_sync::CacheSyncModel,
        user::{UserActor, UserGender, UserModel, UserProfile, UserRole},
        user_login_log::UserLoginLogModel,
        Assets,
    },
    services::session_service::open_session_tree,
    utils::traits::{ModifyModelData, ToAccountModel, ToSearchDoc, ToUpdateDoc, ToUserInfo},
    AppError, AppResult, Config, Pagination, PaginationQuery,
};
use actix_session::Session;
use actix_web::Error as WebError;
use chrono::{DateTime as CDateTime, Utc};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{borrow::BorrowMut, str::FromStr};
use std::{fmt::Debug, sync::Arc};
use wither::{
    bson::{doc, oid::ObjectId, DateTime, Document},
    mongodb::{
        options::{FindOneAndUpdateOptions, ReturnDocument, UpdateModifications},
        Database,
    },
    Model,
};

use super::email_service::EmailService;
static USER_SESSION_KEY: &str = "user";
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct UserService {
    mdb: MDB,
    m_client: MClient,
    config: Arc<Config>,
    cdb: sled::Db,
    email: Arc<EmailService>,
}

impl UserService {
    pub fn new(
        config: Arc<Config>,
        mdb: MDB,
        m_client: MClient,
        cdb: sled::Db,
        email: Arc<EmailService>,
    ) -> Self {
        //let (mdb, mclient) = try_new_from_config(&config.mongo)?;
        UserService {
            mdb,
            m_client,
            config,
            cdb,
            email,
        }
    }

    pub fn get_config(&self) -> Arc<Config> {
        self.config.clone()
    }

    pub async fn get_all_users(&self, query: &PaginationQuery) -> AppResult<Pagination<UserModel>> {
        let users = UserModel::find_and_pagination(self.mdb.clone(), None, query).await?;
        Ok(users)
    }
    pub async fn search_all_users<T: Into<Option<Document>>>(
        &self,
        filter: T,
        query: &PaginationQuery,
    ) -> AppResult<Pagination<UserModel>> {
        let filter: Option<Document> = filter.into();
        let res = UserModel::find_and_pagination(self.mdb.clone(), filter, query).await?;
        Ok(res)
    }
    pub async fn user_detail<T: Into<ObjectId>>(&self, user_id: T) -> AppResult<UserModel> {
        let user_id: ObjectId = user_id.into();
        let user = UserModel::find_by_id(&*self.mdb.clone(), user_id, None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        Ok(user)
    }
    pub async fn create_user(&self, query: &CreateUserReq) -> AppResult<UserModel> {
        let mut user_model: UserModel = query.clone().into();
        user_model
            .borrow_mut()
            .save(&*self.mdb.clone(), None)
            .await?;
        Ok(user_model)
    }

    pub async fn get_user_account<T: Into<ObjectId>>(&self, user_id: T) -> AppResult<AccountModel> {
        AccountModel::find_one(
            &*self.mdb.clone(),
            doc! {
                "user_id":user_id.into()
            },
            None,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))
    }
    /// ## registry_user
    /// 注册用户账号，类型为站内登录
    pub async fn registry_user(
        &self,
        req: &RegistryUserReq,
    ) -> AppResult<(UserModel, AccountModel)> {
        match (self.config.app.enable_email_validation, req.code.as_ref()) {
            (false, _) => {}
            (true, Some(code)) => {
                if !self
                    .email
                    .valid_register_code(req.account.clone(), code.clone())
                    .await?
                {
                    return Err(AppError::ConsError(cons_error::MSG_INVALID_CODE));
                }
            }
            _ => {
                return Err(AppError::ConsError(cons_error::MSG_INVALID_CODE));
            }
        }
        let mut account: AccountModel = req.clone().into();
        let mut user: UserModel = req.clone().into();
        let account_coll = AccountModel::collection(&*self.mdb.clone());
        let account_count = account_coll
            .count_documents(
                doc! {
                    "account":&account.account,
                    "type":account.account_type
                },
                None,
            )
            .await?;
        if account_count > 0 {
            return Err(AppError::ConsError(cons_error::MSG_USER_ALREADY_EXIST));
        }
        user.save(&*self.mdb.clone(), None).await?;
        account.user_id = user.id;
        account.save(&*self.mdb.clone(), None).await?;
        Ok((user, account))
    }
    /// ## update_password
    /// 更新用户的账号的密码
    pub async fn update_password<A: Into<ObjectId>, P: ToString>(
        &self,
        account: A,
        password: P,
    ) -> AppResult<AccountModel> {
        let password = password.to_string();
        let mut res = AccountModel::find_one(&*self.mdb.clone(), doc! {"_id":account.into()}, None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        res.set_password_with_salt(&password);
        res.save(&*self.mdb.clone(), None).await?;
        Ok(res)
    }

    /// ## account_password_login
    /// 使用账号密码登录，登录之后会记录登录时间
    pub async fn account_password_login<I: Into<Option<String>>>(
        &self,
        req: &AccountPassowrdLoginReq,
        real_ip: I,
    ) -> AppResult<UserModel> {
        let account = AccountModel::find_one(
            &*self.mdb.clone(),
            Some(doc! {
                "account":req.account.as_str()
            }),
            None,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_USER_ACCOUNT_NOT_VALID))?;

        if !account.valid_password(req.password.as_str()) {
            return Err(AppError::ConsError(cons_error::MSG_USER_ACCOUNT_NOT_VALID));
        }

        let user_id = account
            .user_id
            .ok_or(AppError::ConsError(cons_error::MSG_USER_BREAK))?;
        let mut user = UserModel::find_one(
            &*self.mdb.clone(),
            doc! {
                "_id":user_id
            },
            None,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_USER_BREAK))?;
        user.up_time = Some(DateTime::now());
        {
            let mut up_user = user.clone();
            let up_db = self.mdb.clone();
            tokio::spawn(async move {
                up_user.save(&*up_db, None).await.ok();
            });
        }
        if let Some(ip) = real_ip.into() {
            let log_user_id = user.id;
            let log_db = self.mdb.clone();
            tokio::spawn(async move {
                match save_user_login_log(log_user_id, ip, &*log_db).await {
                    Ok(_) => {}
                    Err(e) => {
                        log::error!("保存用户登陆记录失败: {}", e);
                    }
                };
            });
        }
        Ok(user)
    }

    /// ## 其他平台登陆
    /// 如果账号不存在则会创建
    pub async fn platform_login_or_create<
        T: ToSearchDoc + ToUserInfo + ToUpdateDoc + ToAccountModel + Debug + Clone,
        I: Into<Option<String>>,
    >(
        &self,
        github_user_info: T,
        real_ip: I,
    ) -> AppResult<UserModel> {
        let user_account = {
            let coll = AccountModel::collection(&*self.mdb);
            let new_account = github_user_info.to_account_model();
            let search_doc = github_user_info.to_search_doc();
            let option = FindOneAndUpdateOptions::builder()
                .upsert(true)
                .return_document(ReturnDocument::Before);
            let account_res = coll
                .find_one_and_update(
                    search_doc.clone(),
                    doc! {
                        "$setOnInsert":new_account.document_from_instance()?,
                        "$set":github_user_info.to_update_doc(),
                    },
                    option.build(),
                )
                .await?
                .map(AccountModel::instance_from_document)
                .and_then(Result::ok);
            if let Some(account) = account_res {
                account
            } else {
                // 空的情况就是新建
                match self
                    .create_or_login_user_with_to_user_info(github_user_info.clone())
                    .await
                {
                    Ok(user) => {
                        // user新建好了，现在将account和user绑定起来
                        let option = FindOneAndUpdateOptions::builder()
                            .return_document(ReturnDocument::After);
                        let res = coll
                            .find_one_and_update(
                                search_doc.clone(),
                                doc! {
                                    "$set":{
                                        "user_id":user.id
                                    }
                                },
                                option.build(),
                            )
                            .await
                            .map(|d| d.map(AccountModel::instance_from_document))
                            .ok()
                            .flatten()
                            .and_then(Result::ok);

                        match res {
                            Some(account) => account,
                            None => {
                                log::error!("account 更新失败");
                                coll.delete_one(search_doc, None).await.map_err(|e| {
                                    log::error!("account 删除失败 info: {:?}", github_user_info);
                                    e
                                })?;
                                user.delete(&*self.mdb).await.map_err(|e| {
                                    log::error!("用户删除失败 {}", e);
                                    e
                                })?;
                                return Err(AppError::other_error("创建用户操作失败"));
                            }
                        }
                    }
                    Err(e) => {
                        log::error!("user 创建失败 {}", e);
                        coll.delete_one(search_doc, None).await.map_err(|e| {
                            log::error!("account 删除失败 info: {:?}", github_user_info);
                            e
                        })?;
                        return Err(e);
                    }
                }
            }
        };
        let mut user = if let Some(user_id) = user_account.user_id {
            UserModel::find_by_id(&*self.mdb, user_id, None)
                .await?
                .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?
        } else {
            return Err(AppError::other_error("创建还未完成"));
        };
        {
            let mut mut_account = user_account;
            let now = DateTime::now();
            mut_account.up_time = Some(now);
            user.up_time = Some(now);
            // 更新登陆时间并且尝试更新用户邮箱
            let email = mut_account.try_get_email();
            if user.email.is_none() && email.is_some() {
                user.email = email.clone();
                user.modifyed();
            }
            let share_db = self.mdb.clone();
            let share_db_2 = self.mdb.clone();
            // account的登录时间记录并不是特别重要，直接开新的任务去执行
            tokio::spawn(async move {
                match mut_account.save(&*share_db_2, None).await {
                    Ok(_) => {}
                    Err(e) => {
                        log::error!("更新用户登录时间失败 error: {}", e);
                    }
                }
            });
            // 这里采用等待的操作，防止并发更新导致的信息错误问题
            match user.save(&*share_db, None).await {
                Ok(_) => {}
                Err(e) => {
                    log::error!("更新用户登录信息失败 error: {}", e);
                }
            };
        }
        {
            // 记录登陆日志则使用新建任务操作
            if let Some(ip) = real_ip.into() {
                let log_user_id = user.id;
                let log_db = self.mdb.clone();
                tokio::spawn(async move {
                    match save_user_login_log(log_user_id, ip, &*log_db).await {
                        Ok(_) => {}
                        Err(e) => {
                            log::error!("保存用户登陆日志失败 {}", e);
                        }
                    }
                });
            }
        }
        Ok(user)
    }
    pub async fn create_or_login_user_with_to_user_info<T: ToUserInfo>(
        &self,
        info: T,
    ) -> AppResult<UserModel> {
        let mut user_info = info.to_user_info();
        user_info.save(&*self.mdb, None).await?;
        Ok(user_info)
    }
    pub async fn update_user_info(&self, req: &UpdateUserInfoReq) -> AppResult<UserModel> {
        let user_id = req.user_id.clone();
        let info_doc: Document = req.info.clone().into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let u = UserModel::find_one_and_update(
            &*self.mdb.clone(),
            doc! {"_id":ObjectId::from_str(user_id.as_str())?},
            UpdateModifications::Document(doc! {"$set":info_doc}),
            options,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        Ok(u)
    }

    pub async fn get_user_roles(
        &self,
        user_id: ObjectId,
        req: &GetUserRolesReq,
    ) -> AppResult<Vec<UserActor>> {
        let user = UserModel::find_one(
            &*self.mdb.clone(),
            Some(doc! {
                "_id":user_id
            }),
            None,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        let res = user
            .actors
            .into_iter()
            .filter(|v| {
                match (v.club_id, &req.club_id) {
                    (Some(oid), Some(club_id)) => {
                        if &oid.to_string() != club_id {
                            return false;
                        }
                    }
                    (_, None) => {}
                    _ => {
                        return false;
                    }
                }
                if let Some(ref actor_type) = req.action_type {
                    if *actor_type & (v.role as u8) == 0 {
                        return false;
                    }
                }
                true
            })
            .collect::<Vec<_>>();
        Ok(res)
    }

    pub async fn check_user_role<I: Into<ObjectId>>(
        &self,
        user_id: I,
    ) -> AppResult<CheckUserRoleRes> {
        let user_id: ObjectId = user_id.into();
        let user = UserModel::find_one(&*self.mdb.clone(), Some(doc! {"_id":user_id}), None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        let mut res = CheckUserRoleRes::default();
        for actor in user.actors {
            if actor.role & UserRole::Admin as u8 == UserRole::Admin as u8 {
                res.admin = true;
            }

            if actor.role & UserRole::Leader as u8 == UserRole::Leader as u8 {
                res.leader = true;
            }

            if actor.role & UserRole::Student as u8 == UserRole::Student as u8 {
                res.student = true;
            }

            if res.admin && res.leader && res.student {
                break;
            }
        }

        Ok(res)
    }

    pub fn set_user_to_session(session: &Session, user: &UserModel) -> AppResult<()> {
        session.insert(USER_SESSION_KEY, Some(user))?;
        Ok(())
    }

    pub fn remove_user_from_session(session: &Session) -> Result<(), WebError> {
        session.insert(USER_SESSION_KEY, Value::Null)
    }

    pub fn get_user_from_session(session: &Session) -> AppResult<UserModel> {
        session
            .get::<UserModel>(USER_SESSION_KEY)
            .ok()
            .flatten()
            .ok_or(AppError::ConsError(
                cons_error::MSG_NOT_FOUND_USER_IN_SESSION,
            ))
    }
    /// ## 更新session中的用户信息
    pub async fn update_user_in_session<T: Into<UserModel>>(&self, user: T) -> AppResult<()> {
        let user: UserModel = user.into();
        let mut user_filter = Document::new();
        user_filter.insert(format!("value.{}.{}", USER_SESSION_KEY, "_id"), user.id);
        let mut caches = CacheSyncModel::find(&*self.mdb.clone(), user_filter, None).await?;
        let tree = open_session_tree(&self.cdb, &*self.config)?;
        // 这里直接序列化user,因为默认在session中就是直接序列化的
        let user_string = serde_json::to_string(&user)?;
        // 逐个读取caches，然后通过每个cache中的c_id从tree中查找本地缓存
        // 将缓存反序列化然后替换其中USER_SESSION_KEY的部分
        // 最后将cache的value也进行更新，然后保存

        while let Some(Ok(mut cache)) = caches.next().await {
            if let Ok(Some(tree_cache)) = tree.get(cache.c_id.as_bytes()) {
                let cache_instance: Option<CacheData> = serde_json::from_slice(&tree_cache).ok();
                if let Some(mut cache_instance) = cache_instance {
                    match &mut cache_instance {
                        CacheData::Expirable(e) => {
                            e.value
                                .insert(USER_SESSION_KEY.to_string(), user_string.clone());
                        }
                        CacheData::Persistent(p) => {
                            p.insert(USER_SESSION_KEY.to_string(), user_string.clone());
                        }
                    }
                    if let Err(e) = tree.insert(
                        cache.c_id.as_bytes(),
                        serde_json::to_string(&cache_instance)?.as_bytes(),
                    ) {
                        log::info!("更新本地缓存失败 {}", e);
                    }
                }
            }
            cache
                .value
                .insert(USER_SESSION_KEY.to_owned(), user.document_from_instance()?);

            cache.save(&*self.mdb, None).await?;
        }
        Ok(())
    }
    /// ## 给用户增加权限
    pub async fn add_actor_to_user<T: Into<ObjectId>, A: Into<UserActor>>(
        &self,
        user_id: T,
        actor: A,
    ) -> AppResult<UserModel> {
        let coll = UserModel::collection(&*self.mdb);
        let user_id: ObjectId = user_id.into();
        let actor: UserActor = actor.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();

        let user = coll
            .find_one_and_update(
                doc! {
                    "_id":user_id
                },
                doc! {
                    "$addToSet":{
                        "actors":actor
                    }
                },
                options,
            )
            .await?
            .map(UserModel::instance_from_document)
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))??;
        self.update_user_in_session(user.clone()).await?;
        Ok(user)
    }
    /// ## 移除用户某个社团的所有权限的权限
    pub async fn remove_all_actor_from_user<T: Into<ObjectId>, C: Into<ObjectId>>(
        &self,
        user_id: T,
        club_id: C,
    ) -> AppResult<UserModel> {
        let club_id: ObjectId = club_id.into();
        let user_id: ObjectId = user_id.into();
        let mut user = UserModel::find_by_id(&*self.mdb, user_id, None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        user.actors = user
            .actors
            .into_iter()
            .filter(|actor| match &actor.club_id {
                None => true,
                Some(id) => id == &club_id,
            })
            .collect::<Vec<_>>();
        user.save(&*self.mdb, None).await?;
        self.update_user_in_session(user.clone()).await?;
        Ok(user)
    }

    /// ## 移除用户的某个角色中的一个或者多个权限
    pub async fn remove_actor_from_user<T: Into<ObjectId>, A: Into<UserActor>>(
        &self,
        user_id: T,
        actor: A,
    ) -> AppResult<UserModel> {
        let user_id: ObjectId = user_id.into();
        let actor: UserActor = actor.into();
        let mut user = UserModel::find_by_id(&*self.mdb, user_id, None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        for user_actor in user.actors.iter_mut() {
            if user_actor.club_id != actor.club_id {
                continue;
            }
            user_actor.role &= !actor.role;
        }
        user.actors = user
            .actors
            .into_iter()
            .filter(|actor| actor.role != 0)
            .collect();
        user.save(&*self.mdb, None).await?;
        self.update_user_in_session(user.clone()).await?;
        Ok(user)
    }
}

mod serde_default {
    use super::*;
    pub fn user_gender() -> UserGender {
        UserGender::Unknown
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateUserReq {
    nickname: String,
    comment: Option<String>,
}
impl From<CreateUserReq> for UserModel {
    fn from(c: CreateUserReq) -> Self {
        Self {
            nickname: c.nickname,
            comment: c.comment,
            ..Default::default()
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryUserReq {
    account: String,
    nickname: String,
    password: String,
    code: Option<String>,
    avatar: Option<String>,
    birthday: Option<CDateTime<Utc>>,
    comment: Option<String>,
    email: Option<String>,
    #[serde(default = "serde_default::user_gender")]
    gender: UserGender,
    name: Option<String>,
    qq: Option<String>,
    wechat: Option<String>,
}
impl From<RegistryUserReq> for AccountModel {
    fn from(a: RegistryUserReq) -> Self {
        let mut ac = AccountModel {
            account: a.account,
            ..AccountModel::default()
        };
        ac.set_salt();
        ac.set_password_with_salt(a.password.as_str());
        ac
    }
}
impl From<RegistryUserReq> for UserModel {
    fn from(r: RegistryUserReq) -> Self {
        UserModel {
            nickname: r.nickname,
            name: r.name,
            birthday: r.birthday.map(|t| t.into()),
            avatar: r.avatar.map(Assets::new_with_site_path),
            gender: r.gender,
            comment: r.comment,
            email: r.email,
            qq: r.qq,
            wechat: r.wechat,
            ..UserModel::default()
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountPassowrdLoginReq {
    pub account: String,
    pub password: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateUserInfoReq {
    pub user_id: String,
    pub info: UserInfoOptions,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfoOptions {
    pub nickname: Option<String>,
    pub avatar: Option<String>,
    pub birthday: Option<CDateTime<Utc>>,
    pub comment: Option<String>,
    pub gender: Option<UserGender>,
    pub name: Option<String>,
    pub qq: Option<String>,
    pub wechat: Option<String>,
    pub profile: Option<UserProfile>,
    pub email: Option<String>,
}
impl From<UserInfoOptions> for Document {
    fn from(u: UserInfoOptions) -> Self {
        let mut document = option_doc!(
            "nickname":u.nickname,
            "comment":u.comment,
            "gender":u.gender,
            "name":u.name,
            "qq":u.qq,
            "wechat":u.wechat,
            "profile":u.profile,
            "email":u.email
        );
        if let Some(avatar) = u.avatar {
            document.insert("avatar", Assets::new_with_site_path(avatar));
        }
        if let Some(birthday) = u.birthday {
            document.insert("birthday", DateTime::from_chrono(birthday));
        }
        document
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetUserRolesReq {
    pub club_id: Option<String>,
    pub action_type: Option<u8>,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CheckUserRoleRes {
    pub admin: bool,
    pub leader: bool,
    pub student: bool,
}
pub async fn save_user_login_log<U: Into<Option<ObjectId>>, I: Into<Option<String>>>(
    user_id: U,
    real_ip: I,
    mdb: &Database,
) -> AppResult<UserLoginLogModel> {
    let mut log_mod = UserLoginLogModel {
        user_id: user_id.into(),
        ip: real_ip.into(),
        ..Default::default()
    };
    log_mod.save(mdb, None).await?;
    Ok(log_mod)
}
